package cc.shacocloud.mirage.utils.date;

import cc.shacocloud.mirage.utils.charSequence.StrUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.*;
import java.util.Date;
import java.util.Optional;
import java.util.TimeZone;
import java.util.regex.Pattern;

/**
 * JDK8+中的{@link LocalDateTime} 工具类封装
 *
 * @author 思追(shaco)
 */
public class LocalDateTimeUtil {
    
    private static final Pattern SS_PATTERN = Pattern.compile("S{1,2}", Pattern.DOTALL);
    
    /**
     * 当前时间，默认时区
     *
     * @return {@link LocalDateTime}
     */
    @Contract(" -> new")
    public static @NotNull LocalDateTime now() {
        return LocalDateTime.now();
    }
    
    /**
     * {@link Instant}转{@link LocalDateTime}，使用默认时区
     *
     * @param instant {@link Instant}
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(Instant instant) {
        return of(instant, ZoneId.systemDefault());
    }
    
    /**
     * {@link Instant}转{@link LocalDateTime}，使用UTC时区
     *
     * @param instant {@link Instant}
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime ofUTC(Instant instant) {
        return of(instant, ZoneId.of("UTC"));
    }
    
    /**
     * {@link ZonedDateTime}转{@link LocalDateTime}
     *
     * @param zonedDateTime {@link ZonedDateTime}
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(ZonedDateTime zonedDateTime) {
        if (null == zonedDateTime) {
            return null;
        }
        return zonedDateTime.toLocalDateTime();
    }
    
    /**
     * {@link Instant}转{@link LocalDateTime}
     *
     * @param instant {@link Instant}
     * @param zoneId  时区
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(Instant instant, ZoneId zoneId) {
        if (null == instant) {
            return null;
        }
        
        return LocalDateTime.ofInstant(instant, Optional.ofNullable(zoneId).orElseGet(ZoneId::systemDefault));
    }
    
    /**
     * {@link Instant}转{@link LocalDateTime}
     *
     * @param instant  {@link Instant}
     * @param timeZone 时区
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(Instant instant, TimeZone timeZone) {
        if (null == instant) {
            return null;
        }
        
        return of(instant, Optional.ofNullable(timeZone).orElseGet(TimeZone::getDefault).toZoneId());
    }
    
    /**
     * 毫秒转{@link LocalDateTime}，使用默认时区
     *
     * <p>注意：此方法使用默认时区，如果非UTC，会产生时间偏移</p>
     *
     * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(long epochMilli) {
        return of(Instant.ofEpochMilli(epochMilli));
    }
    
    /**
     * 毫秒转{@link LocalDateTime}，使用UTC时区
     *
     * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime ofUTC(long epochMilli) {
        return ofUTC(Instant.ofEpochMilli(epochMilli));
    }
    
    /**
     * 毫秒转{@link LocalDateTime}，根据时区不同，结果会产生时间偏移
     *
     * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数
     * @param zoneId     时区
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(long epochMilli, ZoneId zoneId) {
        return of(Instant.ofEpochMilli(epochMilli), zoneId);
    }
    
    /**
     * 毫秒转{@link LocalDateTime}，结果会产生时间偏移
     *
     * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数
     * @param timeZone   时区
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(long epochMilli, TimeZone timeZone) {
        return of(Instant.ofEpochMilli(epochMilli), timeZone);
    }
    
    /**
     * {@link Date}转{@link LocalDateTime}，使用默认时区
     *
     * @param date Date对象
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(Date date) {
        if (null == date) {
            return null;
        }
        
        if (date instanceof DateTime) {
            return of(date.toInstant(), ((DateTime) date).getZoneId());
        }
        return of(date.toInstant());
    }
    
    /**
     * {@link TemporalAccessor}转{@link LocalDateTime}，使用默认时区
     *
     * @param temporalAccessor {@link TemporalAccessor}
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime of(TemporalAccessor temporalAccessor) {
        if (null == temporalAccessor) {
            return null;
        }
        
        if (temporalAccessor instanceof LocalDate) {
            return ((LocalDate) temporalAccessor).atStartOfDay();
        }
        
        return LocalDateTime.of(
                TemporalUtil.get(temporalAccessor, ChronoField.YEAR),
                TemporalUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR),
                TemporalUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH),
                TemporalUtil.get(temporalAccessor, ChronoField.HOUR_OF_DAY),
                TemporalUtil.get(temporalAccessor, ChronoField.MINUTE_OF_HOUR),
                TemporalUtil.get(temporalAccessor, ChronoField.SECOND_OF_MINUTE),
                TemporalUtil.get(temporalAccessor, ChronoField.NANO_OF_SECOND)
        );
    }
    
    /**
     * {@link TemporalAccessor}转{@link LocalDate}，使用默认时区
     *
     * @param temporalAccessor {@link TemporalAccessor}
     * @return {@link LocalDate}
     */
    public static LocalDate ofDate(TemporalAccessor temporalAccessor) {
        if (null == temporalAccessor) {
            return null;
        }
        
        if (temporalAccessor instanceof LocalDateTime) {
            return ((LocalDateTime) temporalAccessor).toLocalDate();
        }
        
        return LocalDate.of(
                TemporalUtil.get(temporalAccessor, ChronoField.YEAR),
                TemporalUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR),
                TemporalUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH)
        );
    }
    
    /**
     * 解析日期时间字符串为{@link LocalDateTime}，仅支持yyyy-MM-dd'T'HH:mm:ss格式，例如：2007-12-03T10:15:30
     *
     * @param text 日期时间字符串
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime parse(CharSequence text) {
        return parse(text, (DateTimeFormatter) null);
    }
    
    /**
     * 解析日期时间字符串为{@link LocalDateTime}，格式支持日期时间、日期、时间
     *
     * @param text      日期时间字符串
     * @param formatter 日期格式化器，预定义的格式见：{@link DateTimeFormatter}
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) {
        if (null == text) {
            return null;
        }
        if (null == formatter) {
            return LocalDateTime.parse(text);
        }
        
        return of(formatter.parse(text));
    }
    
    /**
     * 解析日期时间字符串为{@link LocalDateTime}
     *
     * @param text   日期时间字符串
     * @param format 日期格式，类似于yyyy-MM-dd HH:mm:ss,SSS
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime parse(CharSequence text, String format) {
        if (null == text) {
            return null;
        }
        
        DateTimeFormatter formatter = null;
        if (StrUtil.isNotBlank(format)) {
            // 修复yyyyMMddHHmmssSSS格式不能解析的问题
            if (StrUtil.startWithIgnoreEquals(format, DatePattern.PURE_DATETIME_PATTERN)) {
                final String fraction = StrUtil.removePrefix(format, DatePattern.PURE_DATETIME_PATTERN);
                if (SS_PATTERN.matcher(fraction).matches()) {
                    //将yyyyMMddHHmmssS、yyyyMMddHHmmssSS的日期统一替换为yyyyMMddHHmmssSSS格式，用0补
                    text += StrUtil.repeat('0', 3 - fraction.length());
                }
                formatter = new DateTimeFormatterBuilder()
                        .appendPattern(DatePattern.PURE_DATETIME_PATTERN)
                        .appendValue(ChronoField.MILLI_OF_SECOND, 3)
                        .toFormatter();
            } else {
                formatter = DateTimeFormatter.ofPattern(format);
            }
        }
        
        return parse(text, formatter);
    }
    
    /**
     * 解析日期时间字符串为{@link LocalDate}，仅支持yyyy-MM-dd'T'HH:mm:ss格式，例如：2007-12-03T10:15:30
     *
     * @param text 日期时间字符串
     * @return {@link LocalDate}
     */
    public static LocalDate parseDate(CharSequence text) {
        return parseDate(text, (DateTimeFormatter) null);
    }
    
    /**
     * 解析日期时间字符串为{@link LocalDate}，格式支持日期
     *
     * @param text      日期时间字符串
     * @param formatter 日期格式化器，预定义的格式见：{@link DateTimeFormatter}
     * @return {@link LocalDate}
     */
    public static LocalDate parseDate(CharSequence text, DateTimeFormatter formatter) {
        if (null == text) {
            return null;
        }
        if (null == formatter) {
            return LocalDate.parse(text);
        }
        
        return ofDate(formatter.parse(text));
    }
    
    /**
     * 解析日期字符串为{@link LocalDate}
     *
     * @param text   日期字符串
     * @param format 日期格式，类似于yyyy-MM-dd
     * @return {@link LocalDateTime}
     */
    public static LocalDate parseDate(CharSequence text, String format) {
        if (null == text) {
            return null;
        }
        return parseDate(text, DateTimeFormatter.ofPattern(format));
    }
    
    /**
     * 格式化日期时间为yyyy-MM-dd HH:mm:ss格式
     *
     * @param time {@link LocalDateTime}
     * @return 格式化后的字符串
     */
    public static String formatNormal(LocalDateTime time) {
        return format(time, DatePattern.NORM_DATETIME_FORMATTER);
    }
    
    /**
     * 格式化日期时间为指定格式
     *
     * @param time      {@link LocalDateTime}
     * @param formatter 日期格式化器，预定义的格式见：{@link DateTimeFormatter}
     * @return 格式化后的字符串
     */
    public static String format(LocalDateTime time, DateTimeFormatter formatter) {
        return TemporalUtil.format(time, formatter);
    }
    
    /**
     * 格式化日期时间为指定格式
     *
     * @param time   {@link LocalDateTime}
     * @param format 日期格式，类似于yyyy-MM-dd HH:mm:ss,SSS
     * @return 格式化后的字符串
     */
    public static String format(LocalDateTime time, String format) {
        if (null == time) {
            return null;
        }
        return format(time, DateTimeFormatter.ofPattern(format));
    }
    
    /**
     * 格式化日期时间为yyyy-MM-dd格式
     *
     * @param date {@link LocalDate}
     * @return 格式化后的字符串
     */
    public static String formatNormal(LocalDate date) {
        return format(date, DatePattern.NORM_DATE_FORMATTER);
    }
    
    /**
     * 格式化日期时间为指定格式
     *
     * @param date      {@link LocalDate}
     * @param formatter 日期格式化器，预定义的格式见：{@link DateTimeFormatter}
     * @return 格式化后的字符串
     */
    public static String format(LocalDate date, DateTimeFormatter formatter) {
        return TemporalUtil.format(date, formatter);
    }
    
    /**
     * 格式化日期时间为指定格式
     *
     * @param date   {@link LocalDate}
     * @param format 日期格式，类似于yyyy-MM-dd
     * @return 格式化后的字符串
     */
    public static String format(LocalDate date, String format) {
        if (null == date) {
            return null;
        }
        return format(date, DateTimeFormatter.ofPattern(format));
    }
    
    /**
     * 日期偏移,根据field不同加不同值（偏移会修改传入的对象）
     *
     * @param time   {@link LocalDateTime}
     * @param number 偏移量，正数为向后偏移，负数为向前偏移
     * @param field  偏移单位，见{@link ChronoUnit}，不能为null
     * @return 偏移后的日期时间
     */
    public static LocalDateTime offset(LocalDateTime time, long number, TemporalUnit field) {
        if (null == time) {
            return null;
        }
        
        return time.plus(number, field);
    }
    
    /**
     * 获取两个日期的差，如果结束时间早于开始时间，获取结果为负。
     * <p>
     * 返回结果为{@link Duration}对象，通过调用toXXX方法返回相差单位
     *
     * @param startTimeInclude 开始时间（包含）
     * @param endTimeExclude   结束时间（不包含）
     * @return 时间差 {@link Duration}对象
     * @see TemporalUtil#between(Temporal, Temporal)
     */
    public static Duration between(LocalDateTime startTimeInclude, LocalDateTime endTimeExclude) {
        return TemporalUtil.between(startTimeInclude, endTimeExclude);
    }
    
    /**
     * 获取两个日期的差，如果结束时间早于开始时间，获取结果为负。
     * <p>
     * 返回结果为时间差的long值
     *
     * @param startTimeInclude 开始时间（包括）
     * @param endTimeExclude   结束时间（不包括）
     * @param unit             时间差单位
     * @return 时间差
     */
    public static long between(LocalDateTime startTimeInclude, LocalDateTime endTimeExclude, ChronoUnit unit) {
        return TemporalUtil.between(startTimeInclude, endTimeExclude, unit);
    }
    
    /**
     * 获取两个日期的表象时间差，如果结束时间早于开始时间，获取结果为负。
     * <p>
     * 比如2011年2月1日，和2021年8月11日，日相差了10天，月相差6月
     *
     * @param startTimeInclude 开始时间（包括）
     * @param endTimeExclude   结束时间（不包括）
     * @return 时间差
     */
    public static Period betweenPeriod(LocalDate startTimeInclude, LocalDate endTimeExclude) {
        return Period.between(startTimeInclude, endTimeExclude);
    }
    
    /**
     * 修改为一天的开始时间，例如：2020-02-02 00:00:00,000
     *
     * @param time 日期时间
     * @return 一天的开始时间
     */
    @Contract(pure = true)
    public static @NotNull LocalDateTime beginOfDay(@NotNull LocalDateTime time) {
        return time.with(LocalTime.MIN);
    }
    
    /**
     * 修改为一天的结束时间，例如：2020-02-02 23:59:59,999
     *
     * @param time 日期时间
     * @return 一天的结束时间
     */
    @Contract(pure = true)
    public static @NotNull LocalDateTime endOfDay(@NotNull LocalDateTime time) {
        return time.with(LocalTime.MAX);
    }
    
    /**
     * {@link TemporalAccessor}转换为 时间戳（从1970-01-01T00:00:00Z开始的毫秒数）
     *
     * @param temporalAccessor Date对象
     * @return {@link Instant}对象
     * @see TemporalUtil#toEpochMilli(TemporalAccessor)
     */
    public static long toEpochMilli(TemporalAccessor temporalAccessor) {
        return TemporalUtil.toEpochMilli(temporalAccessor);
    }
}
